/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiummath;

import java.util.Random;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.cadmium.Cadmium;

/**
 * This class provides the primitives related to the <tt>java.util.Random</tt> class.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.1
 */
@PrimitiveProvider
public final class Randoms {

    /**
     * No instance of this class.
     */
    private Randoms() {
    } // end empty constructor

    /**
     * Creates a new random number generator.
     * @param ctxt context
     * @param unit ignore
     * @return a new random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_make(final CodeRunner ctxt,
                                                final Value unit)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new Random());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_make(CodeRunner, Value)'

    /**
     * Creates a new random number generator, with given seed.
     * @param ctxt context
     * @param seed seed for random number generator
     * @return a new random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_make_of_int64(final CodeRunner ctxt,
                                                         final Value seed)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new Random(seed.asBlock().asInt64()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_make_of_int64(CodeRunner, Value)'

    /**
     * Returns the next random boolean from the passed random number generator.
     * @param ctxt context
     * @param random random number generator
     * @return the next random boolean from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_boolean(final CodeRunner ctxt,
                                                        final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            return r.nextBoolean() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_boolean(CodeRunner, Value)'

    /**
     * Generates bytes, storing them into a string.
     * @param ctxt context
     * @param random random number generator
     * @param bytes byte array to populate
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_bytes(final CodeRunner ctxt,
                                                      final Value random,
                                                      final Value bytes)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            r.nextBytes(bytes.asBlock().getBytes());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_bytes(CodeRunner, Value, Value)'

    /**
     * Returns the next random float from the passed random number generator.
     * @param ctxt context
     * @param random random number generator
     * @return the next random float from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_float(final CodeRunner ctxt,
                                                      final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            final Block res = Block.createDouble(r.nextDouble());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_float(CodeRunner, Value)'

    /**
     * Returns the next random, Gaussian float from the passed random number generator.<br/>
     * The distribution has a mean of 0.0, and a standard deviation of 1.0.
     * @param ctxt context
     * @param random random number generator
     * @return the next random float from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_gaussian(final CodeRunner ctxt,
                                                         final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            final Block res = Block.createDouble(r.nextGaussian());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_gaussian(CodeRunner, Value)'

    /**
     * Returns the next random int from the passed random number generator.
     * @param ctxt context
     * @param random random number generator
     * @return the next random int from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_int(final CodeRunner ctxt,
                                                    final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            return Value.createFromRawValue(r.nextInt() | 1);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_int(CodeRunner, Value)'

    /**
     * Returns the next random int from the passed random number generator,
     * the value being between O (inclusive), and a passed value (exclusive).
     * @param ctxt context
     * @param random random number generator
     * @return the next random int from the passed random number generator,
     *         between O (inclusive), and <i>n</i> (exclusive).
     * @throws Fail.Exception <i>n</i> is negative
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_int_n(final CodeRunner ctxt,
                                                      final Value random,
                                                      final Value n)
        throws Fail.Exception {
        if (n.asLong() < 0) {
            Fail.invalidArgument("CadmiumMath.Random.next_int_n");
        } // end if
        try {
            final Random r = (Random) random.asBlock().asCustom();
            return Value.createFromLong(r.nextInt(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_int_n(CodeRunner, Value, Value)'

    /**
     * Returns the next random int32 from the passed random number generator.
     * @param ctxt context
     * @param random random number generator
     * @return the next random int32 from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_int32(final CodeRunner ctxt,
                                                      final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_32_SIZE,
                                                 Custom.INT_32_OPS);
            res.setInt32(r.nextInt());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_int32(CodeRunner, Value)'

    /**
     * Returns the next random int32 from the passed random number generator,
     * the value being between O (inclusive), and a passed value (exclusive).
     * @param ctxt context
     * @param random random number generator
     * @return the next random int32 from the passed random number generator,
     *         between O (inclusive), and <i>n</i> (exclusive).
     * @throws Fail.Exception <i>n</i> is negative
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_int32_n(final CodeRunner ctxt,
                                                        final Value random,
                                                        final Value n)
        throws Fail.Exception {
        if (n.asBlock().asInt32() < 0) {
            Fail.invalidArgument("CadmiumMath.Random.next_int32_n");
        } // end if
        try {
            final Random r = (Random) random.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_32_SIZE,
                                                 Custom.INT_32_OPS);
            res.setInt32(r.nextInt(n.asBlock().asInt32()));
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_int32_n(CodeRunner, Value, Value)'

    /**
     * Returns the next random int64 from the passed random number generator.
     * @param ctxt context
     * @param random random number generator
     * @return the next random int64 from the passed random number generator
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_next_int64(final CodeRunner ctxt,
                                                      final Value random)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_64_SIZE,
                                                 Custom.INT_64_OPS);
            res.setInt64(r.nextLong());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_next_int64(CodeRunner, Value)'

    /**
     * Sets the seed of a random number generator.
     * @param ctxt context
     * @param random random number generator
     * @param seed new seed
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_random_set_seed(final CodeRunner ctxt,
                                                    final Value random,
                                                    final Value seed)
        throws Fail.Exception {
        try {
            final Random r = (Random) random.asBlock().asCustom();
            r.setSeed(seed.asBlock().asInt64());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_random_set_seed(CodeRunner, Value, Value)'

} // end class 'Randoms'
